package DBIx::Schemas;

use strict;
use warnings;

use Data::Dumper;
use SQL::Abstract;
use DBI;
use Module::Find;

use Carp;

=head2 import


=cut

sub import {
    my $class            = shift;
    my %required_schemas = @_;

    # If we had required schemas passed in then run the upgrade.
    foreach my $schema ( sort keys %required_schemas ) {
        $class->check_schema( $schema => $required_schemas{$schema} );
    }

  # warn Dumper( { class => $class, required_schemas => \%required_schemas, } );

    return;
}

=head2 check_schema

  my $result = $class->check_schema( $schema => $version );

Check that the schema is the required version. If it is too low try to upgrade
it.

=cut

sub check_schema {
    my $class            = shift;
    my $schema           = shift;
    my $required_version = shift;

    # use the base schema and then get the version.
    my $schema_class = $class->use_schema($schema);
    my $version      = $schema_class->schema_version;

    if ( $version < $required_version ) {
        $schema_class->upgrade_to($required_version);
    }

    return 1;
}

=head2 use_schema

  my $schema_class = $class->use_schema( $schema_name );

Returns the schema class, having ensured that it is loaded.

=cut

sub use_schema {
    my $class       = shift;
    my $schema_name = shift;

    my $module_name = $class . '::' . $schema_name;

    eval "use $module_name";
    croak $@ if $@;

    return $module_name;
}

=head2 schema_version

  my $version = $schema_class->schema_version(  );

Returns the schema version from the database.

=cut

sub schema_version {
    my $schema_class = shift;
    my $version      = undef;

    my ( $sql, @bind ) = $schema_class->sql_abstract->select(
        $schema_class->metadata_table_name,         # table
        [ $schema_class->metadata_value_field ],    # fields
        { $schema_class->metadata_key_field => 'schema_value' },    # where
    );

    my $dbh = $schema_class->dbh;

    # the table may not exist - run in eval to catch errors.
    eval {
        local $SIG{__WARN__} = sub { 1 };    # catch warnings
        $version = $dbh->selectrow_array( $sql, undef, \@bind );
    };

    return $version || 0;
}

=head2 sql_abstract

  my $sql = $class->sql_abstract(  );

Returns a new SQL::Abstract object.

=cut

sub sql_abstract {
    return SQL::Abstract->new;
}

sub metadata_table_name  { '__metadata'; }
sub metadata_key_field   { 'key'; }
sub metadata_value_field { 'value'; }

=head2 dbh

    my $dbh = $schema_class->dbh;

Returns the database handle for the schema_class.

=cut

sub dbh {
    my $schema_class = shift;
    my $dbh          = DBI->connect( $schema_class->connect_args );
    return $dbh;
}

=head2 upgrade_to

  my $result = $schema_class->upgrade_to( $required_version );

Upgrades the schema to the required version.

=cut

sub upgrade_to {
    my $schema_class     = shift;
    my $required_version = shift;
    my $current_version  = $schema_class->schema_version;

    while ( $current_version < $required_version ) {
        my $next_version = $current_version + 1;
        warn "upgrading from $current_version to $next_version";

        my $version_class = $schema_class->version_class($next_version);
        $version_class->run;

        $current_version = $schema_class->schema_version;
        croak "upgrade to $next_version failed"
          if $current_version != $next_version;
    }

    return;
}

=head2 version_class

  my $version_class = $schema_class->version_class( $version );

Returns the module name for the required version of this schema class.

croaks if it does not exist.

=cut

sub version_class {
    my $schema_class = shift;
    my $version      = shift;

    my @found = findsubmod $schema_class;
    my ($version_class) =
      grep { $schema_class->version_from_module_name($_) == $version } @found;

    croak "Could not find a module for version $version of $schema_class"
      unless $version_class;

    eval "use $version_class";
    croak $@ if $@;

    return $version_class;
}

=head2 version_from_module_name

  my $version = $class->version_from_module_name( $module_name );

Returns the version number found in the module name.
=cut

sub version_from_module_name {
    my $self        = shift;
    my $module_name = shift;

    my ($version) = $module_name =~ m{ (\d+) \z }x;

    croak "could not parse version number out of $module_name" unless $version;

    $version += 0;
    return $version;
}

=head2 run

    my $result = $version_class->run;

Run the SQL and code needed to upgrade the schema to the version from this class.

=cut

sub run {
    my $version_class = shift;
    my @rollbacks     = ();

    foreach my $step ( $version_class->steps ) {
        FIXME;
        push @rollbacks, $step->rollbacks;
    }

    return 1;
}

1;
